﻿using UnityEngine;
using System.Collections;

namespace RootMotion.Dynamics {

	/// <summary>
	/// Contains tools for converting 3D Joints.
	/// </summary>
	public static class JointConverter {

		#region Public

		/// <summary>
		/// Converts any 3D joints on the root GameObject and its children to ConfigurableJoints
		/// </summary>
		/// <param name="root">Root.</param>
		public static void ToConfigurable(GameObject root) {
			int count = 0;

			CharacterJoint[] characterJoints = root.GetComponentsInChildren<CharacterJoint>();
			for (int i = 0; i < characterJoints.Length; i++) {
				CharacterToConfigurable(characterJoints[i]);
				count ++;
			}
			
			HingeJoint[] hingeJoints = root.GetComponentsInChildren<HingeJoint>();
			for (int i = 0; i < hingeJoints.Length; i++) {
				HingeToConfigurable(hingeJoints[i]);
				count ++;
			}
			
			FixedJoint[] fixedJoints = root.GetComponentsInChildren<FixedJoint>();
			for (int i = 0; i < fixedJoints.Length; i++) {
				FixedToConfigurable(fixedJoints[i]);
				count ++;
			}
			
			SpringJoint[] springJoints = root.GetComponentsInChildren<SpringJoint>();
			for (int i = 0; i < springJoints.Length; i++) {
				SpringToConfigurable(springJoints[i]);
				count ++;
			}

			if (count > 0) {
				Debug.Log(count.ToString() + " joints were successfully converted to ConfigurableJoints.");
			} else {
				Debug.Log("No joints found in the children of " + root.name + " to convert to ConfigurableJoints.");
			}
		}

		/// <summary>
		/// Replaces a HingeJoint with a ConfigurableJoint.
		/// </summary>
		public static void HingeToConfigurable(HingeJoint src) {
			#if UNITY_EDITOR
			ConfigurableJoint conf = UnityEditor.Undo.AddComponent(src.gameObject, typeof(ConfigurableJoint)) as ConfigurableJoint;
			#else
			ConfigurableJoint conf = src.gameObject.AddComponent<ConfigurableJoint>();
			#endif

			ConvertJoint(ref conf, src as Joint);
			
			conf.secondaryAxis = Vector3.zero;
			
			conf.xMotion = ConfigurableJointMotion.Locked;
			conf.yMotion = ConfigurableJointMotion.Locked;
			conf.zMotion = ConfigurableJointMotion.Locked;
			
			conf.angularXMotion = src.useLimits? ConfigurableJointMotion.Limited: ConfigurableJointMotion.Free;
			conf.angularYMotion = ConfigurableJointMotion.Locked;
			conf.angularZMotion = ConfigurableJointMotion.Locked;
			
			conf.highAngularXLimit = ConvertToHighSoftJointLimit(src.limits, src.spring, src.useSpring);
			conf.angularXLimitSpring = ConvertToSoftJointLimitSpring(src.limits, src.spring, src.useSpring);
			conf.lowAngularXLimit = ConvertToLowSoftJointLimit(src.limits, src.spring, src.useSpring);

			if (src.useMotor) {
				Debug.LogWarning("Can not convert HingeJoint Motor to ConfigurableJoint.");
			}
		
			#if UNITY_EDITOR
			UnityEditor.Undo.DestroyObjectImmediate(src);
			#else
			GameObject.DestroyImmediate(src);
			#endif
		}

		/// <summary>
		/// Replaces a FixedJoint with a ConfigurableJoint.
		/// </summary>
		public static void FixedToConfigurable(FixedJoint src) {
			#if UNITY_EDITOR
			ConfigurableJoint conf = UnityEditor.Undo.AddComponent(src.gameObject, typeof(ConfigurableJoint)) as ConfigurableJoint;
			#else
			ConfigurableJoint conf = src.gameObject.AddComponent<ConfigurableJoint>();
			#endif

			ConvertJoint(ref conf, src as Joint);
			
			conf.secondaryAxis = Vector3.zero;
			
			conf.xMotion = ConfigurableJointMotion.Locked;
			conf.yMotion = ConfigurableJointMotion.Locked;
			conf.zMotion = ConfigurableJointMotion.Locked;
			
			conf.angularXMotion = ConfigurableJointMotion.Locked;
			conf.angularYMotion = ConfigurableJointMotion.Locked;
			conf.angularZMotion = ConfigurableJointMotion.Locked;
			
			#if UNITY_EDITOR
			UnityEditor.Undo.DestroyObjectImmediate(src);
			#else
			GameObject.DestroyImmediate(src);
			#endif
		}

		/// <summary>
		/// Replaces a SpringJoint with a ConfigurableJoint.
		/// </summary>
		public static void SpringToConfigurable(SpringJoint src) {
			#if UNITY_EDITOR
			ConfigurableJoint conf = UnityEditor.Undo.AddComponent(src.gameObject, typeof(ConfigurableJoint)) as ConfigurableJoint;
			#else
			ConfigurableJoint conf = src.gameObject.AddComponent<ConfigurableJoint>();
			#endif

			ConvertJoint(ref conf, src as Joint);
			
			conf.xMotion = ConfigurableJointMotion.Limited;
			conf.yMotion = ConfigurableJointMotion.Limited;
			conf.zMotion = ConfigurableJointMotion.Limited;
			
			conf.angularXMotion = ConfigurableJointMotion.Free;
			conf.angularYMotion = ConfigurableJointMotion.Free;
			conf.angularZMotion = ConfigurableJointMotion.Free;
			
			SoftJointLimit linearLimit = new SoftJointLimit();
			linearLimit.bounciness = 0f;
			linearLimit.limit = src.maxDistance;
			conf.linearLimit = linearLimit;

			SoftJointLimitSpring linearLimitSpring = new SoftJointLimitSpring();
			linearLimitSpring.damper = src.damper;
			linearLimitSpring.spring = src.spring;
			conf.linearLimitSpring = linearLimitSpring;
			
			#if UNITY_EDITOR
			UnityEditor.Undo.DestroyObjectImmediate(src);
			#else
			GameObject.DestroyImmediate(src);
			#endif
		}
		
		/// <summary>
		/// Replaces a CharacterJoint with a ConfigurableJoint.
		/// </summary>
		public static void CharacterToConfigurable(CharacterJoint src) {
			#if UNITY_EDITOR
			ConfigurableJoint conf = UnityEditor.Undo.AddComponent(src.gameObject, typeof(ConfigurableJoint)) as ConfigurableJoint;
			#else
			ConfigurableJoint conf = src.gameObject.AddComponent<ConfigurableJoint>();
			#endif

			ConvertJoint(ref conf, src as Joint);

			conf.secondaryAxis = src.swingAxis;
			
			conf.xMotion = ConfigurableJointMotion.Locked;
			conf.yMotion = ConfigurableJointMotion.Locked;
			conf.zMotion = ConfigurableJointMotion.Locked;
			
			conf.angularXMotion = ConfigurableJointMotion.Limited;
			conf.angularYMotion = ConfigurableJointMotion.Limited;
			conf.angularZMotion = ConfigurableJointMotion.Limited;
			
			conf.highAngularXLimit = CopyLimit(src.highTwistLimit);
			conf.lowAngularXLimit = CopyLimit(src.lowTwistLimit);
			conf.angularYLimit = CopyLimit(src.swing1Limit);
			conf.angularZLimit = CopyLimit(src.swing2Limit);

			conf.angularXLimitSpring = CopyLimitSpring(src.twistLimitSpring);
			conf.angularYZLimitSpring = CopyLimitSpring(src.swingLimitSpring);

			conf.enableCollision = src.enableCollision;

			conf.projectionMode = src.enableProjection? JointProjectionMode.PositionAndRotation: JointProjectionMode.None;
			conf.projectionAngle = src.projectionAngle;
			conf.projectionDistance = src.projectionDistance;
			
			#if UNITY_EDITOR
			UnityEditor.Undo.DestroyObjectImmediate(src);
			#else
			GameObject.DestroyImmediate(src);
			#endif
		}

		#endregion Public
		
		// Common to all joints
		private static void ConvertJoint(ref ConfigurableJoint conf, Joint src) {
			conf.anchor = src.anchor;
			conf.autoConfigureConnectedAnchor = src.autoConfigureConnectedAnchor;
			conf.axis = src.axis;
			conf.breakForce = src.breakForce;
			conf.breakTorque = src.breakTorque;
			conf.connectedAnchor = src.connectedAnchor;
			conf.connectedBody = src.connectedBody;
			conf.enableCollision = src.enableCollision;
		}
		
		// Conversion from JointLimit and JointSpring to high SoftJointLimit
		private static SoftJointLimit ConvertToHighSoftJointLimit(JointLimits src, JointSpring spring, bool useSpring) {
			SoftJointLimit limit = new SoftJointLimit();
			limit.limit = -src.max;
			limit.bounciness = src.bounciness;
			return limit;
		}

		// Conversion from JointLimit and JointSpring to low SoftJointLimit
		private static SoftJointLimit ConvertToLowSoftJointLimit(JointLimits src, JointSpring spring, bool useSpring) {
			SoftJointLimit limit = new SoftJointLimit();
			limit.limit = -src.min;
			limit.bounciness = src.bounciness;
			return limit;
		}

		// Conversion from JointLimit and JointSpring to SoftJointLimitSpring
		private static SoftJointLimitSpring ConvertToSoftJointLimitSpring(JointLimits src, JointSpring spring, bool useSpring) {
			SoftJointLimitSpring limitSpring = new SoftJointLimitSpring();
			limitSpring.damper = useSpring? spring.damper: 0f;
			limitSpring.spring = useSpring? spring.spring: 0f;
			return limitSpring;
		}
		
		// Returns a copy of the specified SoftJointLimit
		private static SoftJointLimit CopyLimit(SoftJointLimit src) {
			SoftJointLimit limit = new SoftJointLimit();
			limit.limit = src.limit;
			limit.bounciness = src.bounciness;
			return limit;
		}

		// Returns a copy of the specified SoftJointLimitSpring
		private static SoftJointLimitSpring CopyLimitSpring(SoftJointLimitSpring src) {
			SoftJointLimitSpring limitSpring = new SoftJointLimitSpring();
			limitSpring.damper = src.damper;
			limitSpring.spring = src.spring;
			return limitSpring;
		}
	}
}